package com.ssd.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;

import com.ssd.api.payment.dto.enums.RefundStatusEnum;
import com.ssd.api.payment.dto.enums.TradingStateEnum;
import com.ssd.exception.LogicException;
import com.ssd.constant.Constants;
import com.ssd.constant.TradingCacheConstant;
import com.ssd.enums.TradingEnum;
import com.ssd.handler.BasicPayHandler;
import com.ssd.handler.BeforePayHandler;
import com.ssd.handler.HandlerFactory;
import com.ssd.model.domain.RefundRecord;
import com.ssd.model.domain.Trading;
import com.ssd.model.dto.RefundRecordDTO;
import com.ssd.model.dto.TradingDTO;
import com.ssd.service.BasicPayService;
import com.ssd.service.RefundRecordService;
import com.ssd.service.TradingService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 支付的基础功能
 */
@Slf4j
@Service
public class BasicPayServiceImpl implements BasicPayService {

    // 用于处理支付前逻辑的处理器
    @Resource
    private BeforePayHandler beforePayHandler;

    // Redis客户端，用于与Redis数据库进行交互
    @Resource
    private RedissonClient redissonClient;

    // 交易服务，负责处理与交易相关的业务逻辑
    @Resource
    private TradingService tradingService;

    // 退款记录服务，负责处理与退款记录相关的业务逻辑
    @Resource
    private RefundRecordService refundRecordService;

    // 标识符生成器，用于生成唯一的标识符
    @Resource
    private IdentifierGenerator identifierGenerator;

    //统一收单线下交易查询
    @Override
    public TradingDTO queryTradingResult(Long tradingOrderNo) throws LogicException {
        //通过交易号查询交易单数据
        Trading trading = this.tradingService.findTradByTradingOrderNo(tradingOrderNo);

        //查询前置处理：检测交易单参数
        this.beforePayHandler.checkQueryTrading(trading);
        //支付状态
        TradingStateEnum tradingState = trading.getTradingState();
        //如果支付成功或支付取消就直接返回
        if (ObjectUtil.equal(tradingState, TradingStateEnum.YJS) || ObjectUtil.equal(tradingState, TradingStateEnum.QXDD)) {
            return BeanUtil.toBean(trading, TradingDTO.class);
        }
        //使用Redis锁来避免并发查询问题
        String key = TradingCacheConstant.QUERY_PAY + tradingOrderNo;
        RLock lock = redissonClient.getFairLock(key);
        try {
            //获取锁
            if (lock.tryLock(TradingCacheConstant.REDIS_WAIT_TIME, TimeUnit.SECONDS)) {
                //选取不同的支付渠道实现
                BasicPayHandler handler = HandlerFactory.get(trading.getTradingChannel(), BasicPayHandler.class);
                Boolean result = handler.queryTrading(trading);
                if (result) {
                    //如果交易单已经完成，需要将二维码数据删除，节省数据库空间，如果有需要可以再次生成
                    if (ObjectUtil.equal(trading.getTradingState(), TradingStateEnum.YJS) || ObjectUtil.equal(trading.getTradingState(), TradingStateEnum.QXDD)) {
                        trading.setQrCode("");
                    }
                    //更新数据
                    this.tradingService.saveOrUpdate(trading);
                }
                return BeanUtil.toBean(trading, TradingDTO.class);
            }
            //如果未能获取锁，则抛出异常
            throw new LogicException( TradingEnum.NATIVE_QUERY_FAIL.getValue());
        } catch (LogicException e) {
            throw e;
        } catch (Exception e) {
            //记录查询异常日志
            log.error("查询交易单数据异常: trading = {}", trading, e);
            throw new LogicException( TradingEnum.NATIVE_QUERY_FAIL.getValue());
        } finally {
            //释放锁
            lock.unlock();
        }
    }

//    @Override
//    @Transactional
//    public ExecutionResultResDTO refundTradingByTradingOrderNo(Long tradingOrderNo, BigDecimal refundAmount) throws LogicException {
//        //根据业务订单号查看交易单信息
//        Trading trading = this.tradingService.findTradByTradingOrderNo(tradingOrderNo);
//        if(ObjectUtil.isEmpty(trading)){
//            throw new LogicException( TradingEnum.NOT_FOUND.getValue());
//        }
//        //只有已付款的交易单方可退款
//        if(ObjectUtil.notEqual(TradingStateEnum.YJS,trading.getTradingState())){
//            throw new LogicException( TradingEnum.REFUND_FAIL.getValue());
//        }
//
//        ExecutionResultResDTO executionResultResDTO = refundTrading(trading.getTradingOrderNo(), refundAmount);
//        return  executionResultResDTO;
//    }

    // 统一收单交易退款接口
    @Override
    @Transactional
    public RefundRecord refundTrading(Long tradingOrderNo, BigDecimal refundAmount) throws LogicException {
        // 通过交易号查询交易单数据
        Trading trading = this.tradingService.findTradByTradingOrderNo(tradingOrderNo);
        // 入库前置检查
        this.beforePayHandler.checkRefundTrading(trading, refundAmount);
        // 生成缓存键名
        String key = TradingCacheConstant.REFUND_PAY + trading.getTradingOrderNo();
        // 获取Redis公平锁
        RLock lock = redissonClient.getFairLock(key);
        try {
            // 尝试获取锁
            if (lock.tryLock(TradingCacheConstant.REDIS_WAIT_TIME, TimeUnit.SECONDS)) {
                // 对于退款中的记录需要同步退款状态
                syncRefundResult(tradingOrderNo);
                // 查询退款记录
                List<RefundRecord> refundRecordList = this.refundRecordService.findByTradingOrderNo(trading.getTradingOrderNo());
                // 取出退款成功或退款中的记录
                List<RefundRecord> collect = refundRecordList.stream()
                        .filter(r -> StrUtil.equalsAny(r.getRefundStatus().getValue(), RefundStatusEnum.SENDING.getValue(), RefundStatusEnum.SUCCESS.getValue()))
                        .collect(Collectors.toList());
                // 当没有退款成功和退款中的记录时方可继续退款
                if (ObjectUtil.isEmpty(collect)) {
                    // 设置退款金额
                    trading.setRefund(refundAmount);

                    // 创建新的退款记录对象
                    RefundRecord refundRecord = new RefundRecord();
                    // 退款单号
                    refundRecord.setRefundNo(Convert.toLong(this.identifierGenerator.nextId(refundRecord)));
                    refundRecord.setTradingOrderNo(trading.getTradingOrderNo());//交易号
                    refundRecord.setProductOrderNo(trading.getProductOrderNo());//业务订单号
                    refundRecord.setProductAppId(trading.getProductAppId());//业务标识
                    refundRecord.setRefundAmount(refundAmount);//本次退款金额
                    refundRecord.setEnterpriseId(trading.getEnterpriseId());//商户号
                    refundRecord.setTradingChannel(trading.getTradingChannel());//退款渠道
                    refundRecord.setTotal(trading.getTradingAmount());//原订单金额
                    // 初始状态为退款中
                    refundRecord.setRefundStatus(RefundStatusEnum.APPLY_REFUND);
                    // 保存退款记录
                    this.refundRecordService.save(refundRecord);
                    // 设置交易单是退款订单
                    trading.setIsRefund(Constants.YES);
                    // 保存或更新交易单信息
                    this.tradingService.saveOrUpdate(trading);

                    // 请求第三方退款
                    // 选取不同的支付渠道实现
                    BasicPayHandler handler = HandlerFactory.get(refundRecord.getTradingChannel(), BasicPayHandler.class);
                    Boolean result = handler.refundTrading(refundRecord);
                    if (result) {
                        // 更新退款记录数据
                        this.refundRecordService.saveOrUpdate(refundRecord);
                    }
                    return refundRecord;
                }
                // 取出第一条记录返回
                RefundRecord first = CollectionUtil.getFirst(refundRecordList);
                if (ObjectUtil.isNotNull(first)) {
                    return first;
                }
            }
            // 如果获取锁失败或退款条件不满足，抛出异常
            throw new LogicException( TradingEnum.NATIVE_QUERY_FAIL.getValue());
        } catch (LogicException e) {
            throw e;
        } catch (Exception e) {
            // 记录异常日志并抛出LogicException异常
            log.error("查询交易单数据异常:{}", ExceptionUtil.stacktraceToString(e));
            throw new LogicException( TradingEnum.NATIVE_QUERY_FAIL.getValue());
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    /***
     * 对于退款中的记录需要同步退款状态
     * @param tradingOrderNo 交易单号
     */
    @Override
    public void syncRefundResult(Long tradingOrderNo) throws LogicException{
        //查询退款记录
        List<RefundRecord> refundRecordList = this.refundRecordService.findByTradingOrderNo(tradingOrderNo);
        //过滤出退款状态为退款中的记录
        List<RefundRecord> collect = refundRecordList.stream().filter(r -> r.getRefundStatus().equals(RefundStatusEnum.SENDING)).collect(Collectors.toList());

        //如果存在退款中的记录
        if (ObjectUtil.isNotEmpty(collect)) {
            //遍历并查询每笔退款的交易状态
            collect.forEach(v->{
                queryRefundTrading(v.getRefundNo());
            });
        }
    }
    /**
     * 根据退款单号查询退款交易记录
     *
     * 此方法首先通过退款单号获取退款记录，然后进行查询前的预处理
     * 为了确保操作的原子性，使用分布式锁机制避免并发问题
     * 根据交易渠道选择合适的支付处理器来查询退款交易
     * 如果查询成功，更新退款记录并返回其DTO
     *
     * @param refundNo 退款单号，用于查询特定的退款交易记录
     * @return RefundRecordDTO 退款交易记录的DTO对象，包含查询到的数据
     * @throws LogicException 当查询操作失败时抛出此异常
     */
    @Override
    public RefundRecordDTO queryRefundTrading(Long refundNo) throws LogicException {
        //通过退款单号查询交易单数据
        RefundRecord refundRecord = this.refundRecordService.findByRefundNo(refundNo);
        //查询前置处理
        this.beforePayHandler.checkQueryRefundTrading(refundRecord);

        //生成缓存键名
        String key = TradingCacheConstant.REFUND_QUERY_PAY + refundNo;
        //获取分布式公平锁
        RLock lock = redissonClient.getFairLock(key);
        try {
            //尝试获取锁，等待指定时间
            if (lock.tryLock(TradingCacheConstant.REDIS_WAIT_TIME, TimeUnit.SECONDS)) {

                //根据交易渠道选择支付处理器
                BasicPayHandler handler = HandlerFactory.get(refundRecord.getTradingChannel(), BasicPayHandler.class);
                //使用选定的处理器查询退款交易
                Boolean result = handler.queryRefundTrading(refundRecord);
                if (result) {
                    //如果查询成功，保存或更新数据
                    this.refundRecordService.saveOrUpdate(refundRecord);
                }
                //将查询到的数据转换为DTO并返回
                return BeanUtil.toBean(refundRecord, RefundRecordDTO.class);
            }
            //如果未能获取锁，抛出异常表示交易失败
            throw new LogicException( TradingEnum.REFUND_FAIL.getValue());
        } catch (LogicException e) {
            //直接抛出LogicException异常
            throw e;
        } catch (Exception e) {
            //记录其他异常信息，并抛出自定义异常
            log.error("查询退款交易单数据异常: refundRecord = {}", refundRecord, e);
            throw new LogicException( TradingEnum.REFUND_FAIL.getValue());
        } finally {
            //无论是否获取到锁，最终都要释放锁
            lock.unlock();
        }
    }

    /**
     * 关闭交易单
     * 此方法用于关闭一个交易单，通常在交易完成后调用，以确保交易状态的正确性
     * @param tradingOrderNo 交易单号
     * @return 是否成功
     */
    @Override
    public Boolean closeTrading(Long tradingOrderNo) throws LogicException {
        //通过交易号查询交易单数据
        Trading trading = this.tradingService.findTradByTradingOrderNo(tradingOrderNo);
        if (ObjectUtil.isEmpty(trading)) {
            //如果交易单不存在，直接返回成功
            return true;
        }

        //入库前置检查
        //在关闭交易单之前，进行一些必要的检查，确保交易可以被关闭
        this.beforePayHandler.checkCloseTrading(trading);

        //生成锁的键名，用于 Redis 分布式锁，以交易单号为标识，确保同一时间只有一个线程可以处理关闭交易
        String key = TradingCacheConstant.CLOSE_PAY + trading.getTradingOrderNo();
        RLock lock = redissonClient.getFairLock(key);
        try {
            //尝试获取锁，如果成功，则执行关闭交易的逻辑
            if (lock.tryLock(TradingCacheConstant.REDIS_WAIT_TIME, TimeUnit.SECONDS)) {

                //根据交易渠道选择合适的处理程序
                BasicPayHandler handler = HandlerFactory.get(trading.getTradingChannel(), BasicPayHandler.class);
                Boolean result = handler.closeTrading(trading);
                if (result) {
                    //如果关闭成功，清除二维码信息，并保存更新
                    trading.setQrCode("");
                    this.tradingService.saveOrUpdate(trading);
                }
                //返回成功
                return true;
            }
            //如果获取锁失败，抛出异常
            throw new LogicException( TradingEnum.NATIVE_QUERY_FAIL.getValue());
        } catch (LogicException e) {
            //捕获自定义异常，直接抛出
            throw e;
        } catch (Exception e) {
            //捕获其他异常，记录日志并抛出自定义异常
            log.error("查询交易单数据异常:{}", ExceptionUtil.stacktraceToString(e));
            throw new LogicException( TradingEnum.NATIVE_QUERY_FAIL.getValue());
        } finally {
            //释放锁，确保锁被释放，防止死锁
            lock.unlock();
        }
    }
}
